<?php

/**
 * This file is part of the Codizer package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright   Copyright (c) 2010 Advisa (http://www.advisa.fr)
 * @author      Codizer Core Team <team@codizer.org>
 * @package     Codizer
 */

class f_builder_DocumentBuilder
{
	/**
	 * @var f_xml_Xpath
	 */
	private $xmlObject;
	private $extends;
	private $serviceClassName;
	private $className;
	private $tableName;
	private $model;
	private $baseFilename;
	private $extendFilename;
	private $serviceFilename;
	private $moduleName;
	private $documentName;
	private $vars;
	private $isRevisable = "false";
	private $localizationProperties;
	private $isLocalized = "false";
	private $templateFolder;
	private $defaultStatus;
	private $relationVars;
	private $extendProperties;
	private $inWebapp = false;
    private $confirm = true;
	
	/**
	 * @param f_xml_Xpath $xmlObject
	 * @return f_builder_DocumentBuilder
	 */
	private function __construct(f_xml_Xpath $xmlObject)
	{		
		if(is_object($xmlObject) && ($xmlObject instanceof f_xml_Xpath))
		{
			$this->xmlObject = $xmlObject;
		}
		else
		{
			throw new Exception("XML is not valid");
		}
		return $this;
	}
	
	/**
	 * Get instance
	 *
	 * @param f_xml_Xpath $xmlObject
	 * @return f_builder_DocumentBuilder
	 */
	public static function getInstance(f_xml_Xpath $xmlObject)
	{
		return new self($xmlObject);	
	}

    public function noConfirm()
    {
        $this->confirm = false;
        return $this;
    }
		
	/**
	 * Sets initial document variables
	 *
	 */
	private function setVariables()
	{
		$attributes = $this->xmlObject->fromXpath("document")->getAttributes();
		
		try
		{
			$prefix = "m_";
			$dir = MODULES_DIR;
			
			if($this->inWebapp == true)
			{
				$prefix = "w_";
				$dir = WEBAPP_MODULES_DIR;
			}
			
			// Builder template folder
			$this->templateFolder = FRAMEWORK_HOME.DS."builder".DS."templates".DS;
			
			// Document name
			$this->documentName = $this->getDocumentName($this->xmlObject);
			
			// Module name
			$this->moduleName = $this->getModuleName($this->xmlObject);
			
			// Extends class
			$this->extends = $this->getExtendValue($this->xmlObject);
						
			// Classes names
			$this->className = $prefix.$this->moduleName."_documents_".ucfirst($this->documentName);
			$this->serviceClassName = $prefix.$this->moduleName."_services_".ucfirst($this->documentName)."Service";
			$this->baseClassName = $prefix.$this->moduleName."_documents_".ucfirst($this->documentName)."Base";
					
			// Model name
			$this->model = 'modules_'.$this->moduleName.'/'.$this->documentName;
			
			// Base table name
			$this->tableName = 'm_'.$this->moduleName.'__'.$this->documentName;
			
			// In case of extended documents
			if($this->extends != 'f_document_BaseDocument')
			{
				$this->tableName = $this->getFinalTableName($this->extends,$this->xmlObject,$prefix);
				$this->extendProperties = $this->getParentsProperties($this->extends, $extendProperties = array());
			}
			
			// Generated files names
			$this->baseFilename = $dir.DS.$this->moduleName.DS."documents".DS.ucfirst($this->documentName)."Base.class.php";
			$this->serviceFilename = $dir.DS.$this->moduleName.DS."services".DS.ucfirst($this->documentName)."Service.class.php";
			$this->extendFilename = $dir.DS.$this->moduleName.DS."documents".DS.ucfirst($this->documentName).".class.php";
						
			// Additional properties			
			$this->properties = $this->xmlObject->fromXpath("properties/add")->getElements();
			
			// Default document status
			$this->defaultStatus = $this->getDefaultStatus();
			
			// Revision status
			if($this->isRevisable())
			{
				$this->isRevisable = "true";	
			}		
			
			// Localization status
			if($this->isLocalized())
			{
				$this->isLocalized = "true";				
				$this->localizationProperties = $this->xmlObject->fromXpath("localization/field")->getElements();	
			}

		}
		catch(Exception $e)
		{
			throw new Exception($e);
		}
	}
	
	/**
	 * Retrieve document name from the xml file 
	 * 
	 * @return string
	 */	
	private function getDocumentName($xmlObject)
	{
		$attributes = $xmlObject->fromXpath("document")->getAttributes();
		
		if(!isset($attributes["name"]) || is_null($attributes["name"]) || $attributes["name"] == "")
		{
			$tmp = explode(DS, $xmlObject->getXmlFilePath());
			return str_replace(".xml","",end($tmp));
		}		
		
		return $attributes["name"];
	}
	
	/**
	 * Retrieve module name from the xml file 
	 * 
	 * @return string
	 */
	private function getModuleName($xmlObject)
	{
		$attributes = $xmlObject->fromXpath("document")->getAttributes();
		
		if(!isset($attributes["module"]) || is_null($attributes["module"]) || $attributes["module"] == "")
		{
			$fileDetails = f_file_File::getInstance($xmlObject->getXmlFilePath());
					
			$filename = $fileDetails->getFileName();
			$dirname = $fileDetails->getDir();
			
			if(strstr($dirname, MODULES_DIR))
			{
				$dirname = str_replace(MODULES_DIR.DS, "", $dirname);
			}
			else
			{
				$dirname = str_replace(WEBAPP_MODULES_DIR.DS, "", $dirname);
			}
			
			$vars = explode(DS, $dirname);
			
			return $vars[0];				
		}

		return $attributes["module"];
	}

	/**
	 * Retrieve extend value from the xml file 
	 * 
	 * @return string
	 */	
	private function getExtendValue($xmlObject)
	{
		$attributes = $xmlObject->fromXpath("document")->getAttributes();
		
		if(isset($attributes["extends"]) && !is_null($attributes["extends"]) && $attributes["extends"] != "")
		{
			return $attributes["extends"];				
		}
		
		return "f_document_BaseDocument";	
	}
		
	/**
	 * Main building method
	 */
	public function build()
	{
		$this->setVariables();
		$this->buildBaseDocument();	
		$this->buildExtendDocument();
		$this->buildServiceDocument();
	}
	
	/**
	 * Building base document
	 */
	private function buildBaseDocument()
	{
		$template = f_template_Template::getInstance()->disableCache();
		
		$templateFile = $this->templateFolder."document_Base.tpl";
		
		$template->setTemplate($templateFile);
		
		$template->assign("baseClassName", $this->baseClassName);
		$template->assign("extends", $this->extends);
		$template->assign("tableName", $this->tableName);
		$template->assign("model", $this->model);
		$template->assign("declarations", $this->buildVarsDeclaration());
		$template->assign("allFields", $this->buildVarsBag());
		$template->assign("defaultStatus", $this->defaultStatus);
		$template->assign("methods", $this->buildMethods());
		$template->assign("isrevisable", $this->isRevisable);
		$template->assign("isLocalized", $this->isLocalized);

		$content = $template->render();

		$this->writeFile($this->baseFilename, $content, true);
	}
	
	/**
	 * Extend builded document
	 */
	private function buildExtendDocument()
	{
		$template = f_template_Template::getInstance();

		$templateFile = $this->templateFolder."document_Extend.tpl";
		
		$template->setTemplate($templateFile)->disableCache();
		
		$template->assign("className", $this->className);
		$template->assign("baseClassName", $this->baseClassName);
		
		$content = $template->render();
		
		$this->writeFile($this->extendFilename, $content);
	}
	
	/**
	 * Building service class
	 */
	private function buildServiceDocument()
	{
		$template = f_template_Template::getInstance();

		$templateFile = $this->templateFolder."document_Service.tpl";
		
		$template->setTemplate($templateFile)->disableCache();
		
		$template->assign("serviceClassName", $this->serviceClassName);
		
		$content = $template->render();
		
		$path = MODULES_DIR.DS.$this->moduleName.DS."services";
		
		if(!is_dir($path))
		{
			if(CLI_MODE && $this->confirm) echo COLOR_GREEN."Writing".COLOR_NORMAL." -> create path $path\n";
			mkdir($path);
		}			
		
		$this->writeFile($this->serviceFilename, $content);
	}
	
	/**
	 * Building variables definition
	 *
	 * @return string
	 */
	private function buildVarsDeclaration()
	{
		$declaration = "";
		
		if(!is_null($this->properties))
		{
			/* @var $var f_xml_XmlElement */
			foreach($this->properties as $var)
			{
				$attributes = $var->getAttributes();
				
				if(!preg_match("#modules_(.*)/(.*)#", strtolower($attributes['type'])))
				{
					$declaration .= "	private $".$var->getAttributeValue("name");

                    if(isset($attributes['default']))
                    {
                        $declaration .= ' = \''.$attributes['default'].'\'';
                    }

                    $declaration .= ";"."\n";
				}
				else
				{
					$declaration .= "	private $".$var->getAttributeValue("name")." = array();"."\n";
				}
			}
		}
		
		return $declaration;
	}
	
	private function buildVarsBag()
	{
		$bag = array();
		
		if(!is_null($this->properties))
		{
			/* @var $var f_xml_XmlElement */
			foreach($this->properties as $var)
			{
				$bag[] = $var->getAttributeValue("name");
			}
		}

		$defaultFields = array(	"document_id",
                    "document_uid",
					"document_model", 
					"document_author", 
					"document_creationdate", 
					"document_modificationdate", 
					"document_publicationstatus", 
					"document_lang",
					"document_revision",
					"document_startpublicationdate",
					"document_endpublicationdate",
					"document_label");
		
		$fields = array_merge($defaultFields, $bag);
		
		$result = "return array('".join("', \r			'",$fields)."');";
		
		return $result;
	}
	
	/**
	 * Build methods
	 *
	 * @return string
	 */
	private function buildMethods()
	{
		$content = "";
		
		if($this->isRevisable())
		{
			$template = f_template_Template::getInstance();

			$templateFile = $this->templateFolder."document_Revision.tpl";
		
			$template->setTemplate($templateFile)->disableCache();
			
			$template->assign("className", $this->className);
			$template->assign("revisionTableName", $this->tableName."_revisions");
			
			$content .= $template->render();
		}
		
		if(!is_null($this->properties))
		{
			// Build methods for each type of variable
			/* @var $var f_xml_XmlElement */
			foreach($this->properties as $var)
			{
				$attributes = $var->getAttributes();

				$params = array();
				$params['methodName'] = ucfirst($attributes['name']);
				$params['variable'] = $attributes['name'];
				$params['field'] = $attributes['name'];
				
				if (isset($attributes['format']))
				{
				  	$params['format'] = $attributes['format'];	
				}
								
				$type = strtolower($attributes['type']);
				
				if(preg_match("#modules_(.*)/(.*)#", $type))
				{
					$type = $attributes['type'];
				}
				
				$content .= $this->buildMethodByType($type, $params);
			}
		}	

		// build a method to get related fields
		$content .= $this->buildGetRelationFieldsMethod();

		if($this->isLocalized == "true")
		{
			$content .= $this->buildLocalizationMethods();
		}
		
		return $content;
	}
		
	/**
	 * Build extra methods for localized documents
	 *
	 * @return string
	 */
	private function buildLocalizationMethods()
	{
		$template = f_template_Template::getInstance();
		
		$templateFile = $this->templateFolder."variable_Localization.tpl";
		
		$template->setTemplate($templateFile)->disableCache();

		if(isset($this->localizationProperties[0]))
		{
			/* @var $localeProperty f_xml_XmlElement */
			foreach($this->localizationProperties as $localeProperty)
			{			
				$localizationFields[] = $localeProperty->getAttributeValue("name");
			}		
		}
		
		$allFields = array(	"document_id",
                            "document_uid",
							"document_model", 
							"document_author", 
							"document_creationdate", 
							"document_modificationdate",
                            "document_publicationstatus",
							"document_lang",
							"document_revision",
							"document_startpublicationdate",
							"document_endpublicationdate",
							"document_label");
		
		if(!is_null($this->properties))
		{
			/* @var $property f_xml_XmlElement */
			foreach($this->properties as $property)
			{
				$allFields[] = $property->getAttributeValue("name");
			}
		}

        $nonLocalizedFields = array_diff($allFields, $localizationFields);
        $localizationFields = join("', '",$localizationFields);
        $nonLocalizedFields = join("', \r			'",$nonLocalizedFields);
		
		$template->assign("localizedFields", $localizationFields);
		$template->assign("nonLocalizedFields", $nonLocalizedFields);
		$template->assign("className", $this->className);
		
		return $template->render();
	}

	private function buildGetRelationFieldsMethod()
	{
		$template = f_template_Template::getInstance();
		
		$templateFile = $this->templateFolder."variable_RelatedVars.tpl";
		
		$template->setTemplate($templateFile)->disableCache();
		
		$relationVars = "";
		if(count($this->relationVars) > 0)
		{
			$relationVars = join("', '", $this->relationVars);
		}
		else
		{
			$relationVars = null;
		}
		
		$template->assign("relationVars", $relationVars);
				
		return $template->render();
	}
	
	/**
	 * Build a method by Type
	 *
	 * @param string $type
	 * @param array $params
	 */
	private function buildMethodByType($type, $params)
	{
		$template = f_template_Template::getInstance();

		// Building methods for standard type
		if(!preg_match("#modules_(.*)/(.*)#", $type))
		{
			$templateFile = $this->templateFolder."variable_".ucfirst($type).".tpl";
			
			$template->setTemplate($templateFile)->disableCache();
			
			$template->assign("methodName", $params['methodName']);
			$template->assign("variable", $params['variable']);
			$template->assign("className", $this->className);
      //specific to decimal format			
      if(strtolower($type) == "decimal")
      {
        if (isset($params['format']))
        {
          $decimalFormat = explode(",", $params['format']);
          if (count($decimalFormat) == 2) 
          {
            $numDigit = $decimalFormat[count($decimalFormat)-1];
          }
          else
          {
            $numDigit = 2;
          }
        }
	      $template->assign("digit", $numDigit);	
			}

			return $template->render();			
		}
		// Building method for related documents
		else
		{
			$documentProvider = f_document_Provider::getInstance();
			$templateFile = $this->templateFolder."variable_RelatedDocument.tpl";
			$template->setTemplate($templateFile)->disableCache();
									
			$this->relationVars[] = $params['field']; 
			
			$template->assign("methodName", $params['methodName']);
			$template->assign("variable", $params['variable']);
			$template->assign("documentType", $documentProvider->resolveClassName($type));
			$template->assign("field", $params['field']);
			$template->assign("className", $this->className);

			return $template->render();
		}
	}
	
	/**
	 * Get the default status
	 *
	 * @return string status
	 */
	private function getDefaultStatus()
	{
		$authorized = array("DRAFT", "PUBLISHED");
		
		$defaultStatus = strtoupper($this->xmlObject->fromXpath("statuses")->getAttributeValue("default", "DRAFT"));
		
		if(in_array($defaultStatus, $authorized))
		{
			return $defaultStatus;
		}
		return "DRAFT";
	}
	
	/**
	 * Check if document is revisable
	 *
	 * @return boolean
	 */
	private function isRevisable()
	{
		$value = $this->xmlObject->fromXpath("revision")->getAttributeValue("isrevisable");
		$value == "true" ? $val = true : $val = false;
		return $val;
	}
		
	/**
	 * Check if document is localized
	 *
	 * @return boolean
	 */
	private function isLocalized()
	{
        if($this->xmlObject->fromXpath("localization/field")->getElements())
        {
            return (count($this->xmlObject->fromXpath("localization/field")->getElements()) > 0);
        }
        return false;
	}
	
	/**
	 * Set if document is generated in webapp
	 *
	 * @param boolean $boolean
	 * @return f_builder_Documentbuilder
	 */
	public function isInWebapp($boolean)
	{
		$this->inWebapp = $boolean;
		return $this;
	}
	
	/**
	 * Write file
	 *
	 * @param string $fileName
	 * @param string $fileContent
	 */
	private function writeFile($fileName, $fileContent, $forceRewrite = false)
	{
		if($forceRewrite == false)
		{
			if(file_exists($fileName))
			{
				if(CLI_MODE && $this->confirm) echo COLOR_RED."Skipped".COLOR_NORMAL." -> File ".$fileName." already exist, remove if necessary\n";
				return null;
			}
		}

		if(CLI_MODE) echo COLOR_GREEN."Writing".COLOR_NORMAL." -> ".$fileName."\n";
		
		$tmpPath = substr($fileName,0,strrpos($fileName,DS));
		
		if(is_dir($tmpPath) == false)
		{
			mkdir($tmpPath, 0775, true);
        }

        if(!is_readable($tmpPath) || !is_writable($tmpPath))
        {
            $perms = substr(sprintf('%o', fileperms($tmpPath)), -4);
            chmod($tmpPath, 0775);
        }
        
        $fp = fopen($fileName, "w");
        fwrite($fp, $fileContent);
        fclose($fp);

        if(isset($perms))
        {
            chmod($tmpPath, $perms);
        }
	}
	
	/**
	 * Get the xml File path 
	 *
	 * @param string $value - exemple m_module_documents_Documents
	 * @return string
	 */
	private function getXmlFile($value)
	{
		// Split extends value to get the dir, module and document values
		$tmp = explode("_",$value);
		
		if(count($tmp) != 4)
		{
			throw new Exception("bad extends value");
		}
		
		$tmpPrefix = strtolower($tmp[0]);
		
		if($tmpPrefix == "m")
		{
			$dir = MODULES_DIR;
		}
		else
		{
			$dir = WEBAPP_MODULES_DIR;
		}
		
		$module = strtolower($tmp[1]);
		$document = strtolower($tmp[3]);
		
		// Xml file of the extended document
		return $dir.DS.$module.DS."documents".DS.$document.".xml";
		
	}

	/**
	 * Retrieve information about the xml $file  
	 *
	 * @param string $file
	 * @return array
	 */
	private function retrieveInformation($file)
	{
		// If file exist
		if(is_file($file))
		{
			// Read it
			$xmlFileObject = f_xml_Xpath::getInstance($file);
			
			// Get the extends value
			$extends = $this->getExtendValue($xmlFileObject);
			
			// Module
			$moduleName = $this->getModuleName($xmlFileObject);	
					
			// Name
			$documentName = $this->getDocumentName($xmlFileObject);

	
			return array($xmlFileObject, $extends, $moduleName, $documentName);
		}
		else
		{
			throw new Exception("File $file doesn't exist");
		}
	}
	
    /**
	 * Get the Table name of the final parent
	 *
	 * @param string $extends
	 * @param object $xmlObject
	 * @param string $prefix
	 * @return string tableName
	 */
	private function getFinalTableName($extends, $xmlObject, $prefix)
	{
		// Case of extended document
		if($extends != "f_document_BaseDocument")
		{
			// Get the xml file
			$file = $this->getXmlFile($extends);
			
			// Get information about the current document
			list($xmlFileObject, $tmpExtends) = $this->retrieveInformation($file);
			
			// Return his tableName
			return $this->getFinalTableName($tmpExtends, $xmlFileObject, $prefix);
		}
		else
		{	
			// Case of final parent document
						
			// Module
			$moduleName = $this->getModuleName($xmlObject);
							
			// Name
			$documentName = $this->getDocumentName($xmlObject);
								
			return $prefix.$moduleName."_doc_".$documentName;
		}
	}
	
	/**
	 * Get all parents Properties 
	 *
	 * @param string $extends
	 * @param array $properties
	 * @return array
	 */
	private function getParentsProperties($extends, $properties)
	{
		// Case of extended document
		if($extends != "f_document_BaseDocument")
		{
			// Get the xml file
			$file = $this->getXmlFile($extends);

			// Get information about the current document
			list($xmlFileObject, $tmpExtends, $moduleName, $documentName) = $this->retrieveInformation($file);

			// search className of current document
			$tmpModele = "modules_".$moduleName."/".$documentName;
			$className = f_document_Provider::getInstance()->resolveClassName($tmpModele);
			
			// Check if class exist
			if(!class_exists($className))
			{
				if(CLI_MODE && $this->confirm) echo COLOR_BEIGE."Info".COLOR_NORMAL."    -> Be carefull, $className doesn't exist yet. Check it will be generate or generate it.\n";
			}
			
			// Get properties
			$properties[] = $xmlFileObject->fromXpath("properties/add")->getElements();
			
			return $this->getParentsProperties($tmpExtends, $properties);
		}
		else
		{
			return $properties;
		}
	}
}
